package minioUtil

import (
	"bytes"
	"context"
	"devops-super/internal/model/mymodel"
	"encoding/base64"
	"io"
	"log"
	"net/url"
	"os"
	"strings"
	"time"

	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/os/glog"
	"github.com/gogf/gf/v2/util/gconv"
	"github.com/minio/minio-go/v7"
	"github.com/minio/minio-go/v7/pkg/credentials"
)

var (
	UploadOssConfKey    = "upload.oss"
	MinioConfKeyDefault = "oss.default"
)

type FileObject struct {
	BucketName string `json:"bucketName"`
	ObjectName string `json:"objectName"`
	FilePath   string `json:"filePath"` // 上传的临时文件
	FileName   string `json:"fileName"`
}

// minio的bucket 只能小写，并且不能有下划线，所以要进行格式化  大写=》小写  _ => xx
func FormatBucket(bucket string) string {
	// 替换所有 "_" 为 "x"
	result1 := strings.Replace(bucket, "_", "xx", -1)
	return strings.ToLower(result1)
}

func GetMinioConfkeyByServername(ctx context.Context, server string) string {
	return "oss." + server
}

func GetMinioConfig(ctx context.Context, confKey string) *mymodel.MinioItemConf {
	confString, _ := g.Cfg().Get(ctx, confKey)
	var conf = new(mymodel.MinioItemConf)
	_ = gconv.Structs(confString, &conf)
	return conf
}

func GetMinioConfigByServername(ctx context.Context, server string) *mymodel.MinioItemConf {
	confKey := GetMinioConfkeyByServername(ctx, server)
	return GetMinioConfig(ctx, confKey)
}

func getContentType(ext string) string {
	switch ext {
	case "jpg":
		return "image/jpeg"
	case "tiff":
		return "image/tiff"
	case "gif":
		return "image/gif"
	case "png":
		return "image/png"
	case "tif":
		return "image/tif"
	case "jpeg":
		return "image/jpeg"
	default:
		return "application/octet-stream"
	}
}

// PutBytes 将原始字节数组存储到MinIO
// contentType = "application/octet-stream"
func PutBytes(ctx context.Context, confKey string, bucketName, objectName string, data []byte) (size int64, err error) {
	bucketName = FormatBucket(bucketName)

	conf := GetMinioConfig(ctx, confKey)
	// Initialize minio client object.
	minioClient, err := minio.New(conf.Endpoint, &minio.Options{
		Creds:  credentials.NewStaticV4(conf.AccessKey, conf.Secret, ""),
		Secure: conf.UseSSL,
	})
	if err != nil {
		glog.Info(ctx, "minio connect err")
		return
	}

	// 创建字节数组读取器
	reader := bytes.NewReader(data)
	size = int64(len(data))

	// 上传字节数组到MinIO
	_, err = minioClient.PutObject(ctx, bucketName, objectName, reader, size, minio.PutObjectOptions{
		ContentType: "application/octet-stream",
	})
	if err != nil {
		// 如果错误是桶不存在，则创建桶
		if minio.ToErrorResponse(err).Code == "NoSuchBucket" {
			err = CreateBucket(ctx, confKey, bucketName, "zh-east-1")
			if err != nil {
				return 0, err
			}
			// 再重新保存数据
			return PutBytes(ctx, confKey, bucketName, objectName, data)
		}
		glog.Error(ctx, "Failed to put object:", err)
		return 0, err
	}
	return size, nil
}

// GetBytes 从MinIO读取字节数组
func GetBytes(ctx context.Context, confKey string, bucketName, objectName string) (data []byte, err error) {
	bucketName = FormatBucket(bucketName)
	conf := GetMinioConfig(ctx, confKey)
	// Initialize minio client object.
	minioClient, err := minio.New(conf.Endpoint, &minio.Options{
		Creds:  credentials.NewStaticV4(conf.AccessKey, conf.Secret, ""),
		Secure: conf.UseSSL,
	})
	if err != nil {
		glog.Info(ctx, "minio connect err")
		return
	}

	// 获取对象
	object, err := minioClient.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{})
	if err != nil {
		glog.Error(ctx, "Failed to get object:", err)
		return nil, err
	}
	defer object.Close() // 确保流关闭

	// 读取字节数据
	data, err = io.ReadAll(object)
	if err != nil {
		glog.Error(ctx, "Failed to read object data:", err)
		return nil, err
	}

	return data, nil
}

// DeleteObject 删除MinIO中的指定对象
func DeleteObject(ctx context.Context, confKey string, bucketName, objectName string) (err error) {
	bucketName = FormatBucket(bucketName)

	conf := GetMinioConfig(ctx, confKey)
	// Initialize minio client object.
	minioClient, err := minio.New(conf.Endpoint, &minio.Options{
		Creds:  credentials.NewStaticV4(conf.AccessKey, conf.Secret, ""),
		Secure: conf.UseSSL,
	})
	if err != nil {
		glog.Info(ctx, "minio connect err")
		return
	}

	// 删除对象
	err = minioClient.RemoveObject(ctx, bucketName, objectName, minio.RemoveObjectOptions{})
	if err != nil {
		glog.Error(ctx, "Failed to delete object:", err)
		return err
	}

	//glog.Info(ctx, "Successfully deleted object: ", objectName)
	return nil
}

// CreateBucket 创建MinIO存储桶
// region参数可选，若为空则使用MinIO默认区域
// "zh-east-1"
func CreateBucket(ctx context.Context, confKey string, bucketName string, region string) (err error) {
	bucketName = FormatBucket(bucketName)

	conf := GetMinioConfig(ctx, confKey)
	// Initialize minio client object.
	minioClient, err := minio.New(conf.Endpoint, &minio.Options{
		Creds:  credentials.NewStaticV4(conf.AccessKey, conf.Secret, ""),
		Secure: conf.UseSSL,
	})
	if err != nil {
		glog.Info(ctx, "minio connect err")
		return
	}

	// 检查桶是否已存在
	exists, err := minioClient.BucketExists(ctx, bucketName)
	if err != nil {
		glog.Error(ctx, "Failed to check bucket existence:", err)
		return err
	}
	if exists {
		glog.Info(ctx, "Bucket already exists: ", bucketName)
		return nil
	}

	// 创建桶
	err = minioClient.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{
		Region: region,
	})
	if err != nil {
		glog.Error(ctx, "Failed to create bucket:", err)
		return err
	}

	//glog.Info(ctx, "Successfully created bucket: ", bucketName)
	return nil
}

// DeleteBucket 删除MinIO中的指定桶
// 注意：删除桶前必须确保桶为空
func DeleteBucket(ctx context.Context, confKey string, bucketName string) (err error) {
	bucketName = FormatBucket(bucketName)

	conf := GetMinioConfig(ctx, confKey)
	// Initialize minio client object.
	minioClient, err := minio.New(conf.Endpoint, &minio.Options{
		Creds:  credentials.NewStaticV4(conf.AccessKey, conf.Secret, ""),
		Secure: conf.UseSSL,
	})
	if err != nil {
		glog.Info(ctx, "minio connect err")
		return
	}

	// 检查桶是否存在  不需要检查
	//exists, err := minioClient.BucketExists(ctx, bucketName)
	//if err != nil {
	//	glog.Error(ctx, "Failed to check bucket existence:", err)
	//	return err
	//}
	//if !exists {
	//	glog.Info(ctx, "Bucket does not exist: ", bucketName)
	//	return nil
	//}

	// 删除桶
	err = minioClient.RemoveBucket(ctx, bucketName)
	if err != nil {
		//glog.Error(ctx, "Failed to delete bucket:", err)
		return err
	}

	//glog.Info(ctx, "Successfully deleted bucket: ", bucketName)
	return nil
}

func PutBase64(ctx context.Context, confKey string, bucketName, objectName, base64Data string) (size int64, err error) {
	bucketName = FormatBucket(bucketName)

	conf := GetMinioConfig(ctx, confKey)
	// Initialize minio client object.
	minioClient, err := minio.New(conf.Endpoint, &minio.Options{
		Creds:  credentials.NewStaticV4(conf.AccessKey, conf.Secret, ""),
		Secure: conf.UseSSL,
	})
	if err != nil {
		glog.Info(ctx, "minio connect err")
		return
	}

	// 将Base64字符串解码为字节数组

	newStr := strings.Replace(base64Data, "data:image/png;base64,", "", -1)

	data, err := base64.StdEncoding.DecodeString(newStr)
	if err != nil {
		return
	}

	// 创建一个读取字节数组的ReadCloser
	reader := bytes.NewReader(data)

	// 获取字节数组的大小
	size = int64(len(data))

	// 将Base64图像上传到Minio
	_, err = minioClient.PutObject(ctx, bucketName, objectName, reader, size, minio.PutObjectOptions{
		ContentType: "image/jpeg", // 设置正确的内容类型
	})
	if err != nil {
		return
	}
	return size, nil
}

// 根据文件保存对象
func FPutObject(ctx context.Context, confKey string, obj *FileObject) (res bool, err error) {
	conf := GetMinioConfig(ctx, confKey)
	// Initialize minio client object.
	minioClient, err := minio.New(conf.Endpoint, &minio.Options{
		Creds:  credentials.NewStaticV4(conf.AccessKey, conf.Secret, ""),
		Secure: conf.UseSSL,
	})
	if err != nil {
		glog.Info(ctx, "minio connect err")
		return
	}
	bucketName := obj.BucketName
	bucketName = FormatBucket(bucketName)

	location := "beijing"
	exists, errBucketExists := minioClient.BucketExists(ctx, bucketName)
	if errBucketExists == nil && exists {
		glog.Info(ctx, "exists")
	} else {
		err = minioClient.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{Region: location})
		if err != nil {
			glog.Info(ctx, "create bucket err")
			return
		}
	}

	splis := strings.Split(obj.FileName, ".")
	ext := splis[len(splis)-1]

	contentType := getContentType(ext)

	// Upload the zip file with FPutObject
	_, err = minioClient.FPutObject(ctx, bucketName, obj.ObjectName, obj.FilePath, minio.PutObjectOptions{ContentType: contentType})
	if err != nil {
		glog.Info(ctx, "put object err")
		return
	}

	return true, nil
}

// 获取对象url   必须保证是public的
func GetObjectUrl(ctx context.Context, confKey string, obj *FileObject, isAttachment bool) (res string, err error) {
	conf := GetMinioConfig(ctx, confKey)
	// Initialize minio client object.
	minioClient, err := minio.New(conf.Endpoint, &minio.Options{
		Creds:  credentials.NewStaticV4(conf.AccessKey, conf.Secret, ""),
		Secure: conf.UseSSL,
	})
	if err != nil {
		glog.Info(ctx, "minio connect err")
		return
	}
	bucketName := obj.BucketName

	bucketName = FormatBucket(bucketName)

	reqParams := make(url.Values)
	if isAttachment {
		reqParams.Set("response-content-disposition", "attachment; filename=\""+obj.FileName+"\"")
	}
	presignedURL, err := minioClient.PresignedGetObject(ctx, bucketName, obj.ObjectName, time.Second*24*60*60*3, reqParams)
	if err != nil {
		glog.Info(ctx, "PresignedGetObject err")
		return
	}
	arr := strings.Split(presignedURL.RequestURI(), "?")
	url1 := arr[0]
	http := "http://"
	if conf.UseSSL {
		http = "https://"
	}
	res = http + conf.Publicpoint + url1
	return res, nil
}

// 下载对象
func downObject(minioClient *minio.Client, bucketName, objectName, localFileName string) {
	bucketName = FormatBucket(bucketName)

	object, err := minioClient.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
	if err != nil {
		log.Fatalf("download object error " + err.Error())
		return
	}
	localFile, err := os.Create(localFileName)
	if err != nil {
		log.Fatalf("create local object error " + err.Error())
	}
	_, err = io.Copy(localFile, object)
	if err != nil {
		log.Fatalf("write object from object to local file error " + err.Error())
		return
	}
}
